package org.example.JUC;

import java.util.concurrent.TimeUnit;

/**
 * <pre>
 *    Synchronized关键字用于控制线程同步，防止同步代码被多线程同时执行，确保并发安全性。
 *    Synchronized关键字可以用于修饰类、方法、变量
 *    常用使用方式：
 *    1、作用于方法：
 *          实例方法，对该类的当前对象进行加锁
 *          修饰静态方法，相当于对类进行加锁，因为静态方法是类下所有对象共享
 *    2、作用于代码块：指定对象加锁，进入同步代码块前先要获得对象的锁
 *
 *    Synchronized早期获取锁是依赖于操作系统底层的锁实现，java线程要映射到操作系统的原生线程上；
 *    如果要挂起或唤醒一个线程都需要操作系统帮忙完成，而操作系统实现线程切换都需要从用户态切换为内核态，切换时间成本高，效率低。
 *    jdk1.6进行了优化
 *
 *    Synchronized可重入，是指一个线程获取该锁之后，该线程还可以继续获取该锁。
 *    底层实现是维护了一个计数器，线程每进入获取锁一次，计数器+1，释放锁-1，为0时代表该锁未被任何对象持有，其他线程可以竞争。
 * </pre>
 */
public class SynchronizedTest {

    // 测试用于观察，实际使用共享资源不推荐string，jdk对string 有缓存
    private static String resourceA = "resourceA";
    private static String resourceB = "resourceB";

    public static void main(String[] args) throws InterruptedException {

        SynchronizedTest obj = new SynchronizedTest();
        // 0、形成资源相互等待，死锁场景
        obj.scene0();

        // 1、A、B线程访问被Synchronized关键字修饰的非静态方法f
        obj.scene1();

        // 2、A线程访问被Synchronized关键字修饰的非静态方法f1、B线程访问被Synchronized关键字修饰的非静态方法f2
        obj.scene2();

        // 3、A线程访问被Synchronized关键字修饰的静态方法f1、B线程访问被Synchronized关键字修饰的静态方法f2
        obj.scene3();

        // 4、A线程访问被Synchronized关键字修饰的非静态方法f1、B线程访问被Synchronized关键字修饰的静态方法f2
        obj.scene4();

        // 5、可重入演示，启动A、B两线程，每个线程调用了两次同步实例方法f
        obj.scene5();
    }

    private void method() throws InterruptedException {
        System.out.println(Thread.currentThread() + "method");
        TimeUnit.SECONDS.sleep(2);
    }

    private synchronized void method2A() throws InterruptedException {
        System.out.println(Thread.currentThread() + "method2A");
        TimeUnit.SECONDS.sleep(2);
    }

    private synchronized void method2B() throws InterruptedException {
        System.out.println(Thread.currentThread() + "method2B");
        TimeUnit.SECONDS.sleep(2);
    }

    private synchronized static void method3A() throws InterruptedException {
        System.out.println(Thread.currentThread() + "method3A");
        TimeUnit.SECONDS.sleep(2);
    }

    private synchronized static void method3B() throws InterruptedException {
        System.out.println(Thread.currentThread() + "method3B");
        TimeUnit.SECONDS.sleep(2);
    }

    private void scene0() {
        new Worker(resourceA, resourceB).start();
        new Worker(resourceB, resourceA).start();
    }

    private void scene2() {

        new Thread(() -> {
            try {
                method2A();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                method2B();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                method();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void scene1() {
        new Thread(() -> {
            try {
                method2A();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                method2A();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void scene3() {

        new Thread(() -> {
            try {
                SynchronizedTest.method3A();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                SynchronizedTest.method3B();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void scene4() {

        new Thread(() -> {
            try {
                method2A();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                SynchronizedTest.method3A();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void scene5() {

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "-start");
                method2A();
                method2A();
                System.out.println(Thread.currentThread().getName() + "-end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "-start");
                method2A();
                method2A();
                System.out.println(Thread.currentThread().getName() + "-end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();


    }

    static class Worker extends Thread {

        private final String one;
        private final String another;

        public Worker(String one, String another) {
            this.one = one;
            this.another = another;
        }

        @Override
        public void run() {
            synchronized (one) {
                System.out.println(Thread.currentThread().getName() + "- get Resource[" + one + "]");
                synchronized (another) {
                    System.out.println(Thread.currentThread().getName() + "- get Resource[" + another + "]");
                }
            }
        }
    }
}
